/**
 * 
 */
package wdmsim;

import java.util.Vector;

import controlPlane.PCE;

/**
 * @author alissonslp
 *
 */
public class WDMLink extends Link {

	
	private int id;
	private int source;
	private int destination;
	private double delay;
	private int wavelengths;
	private int maxBandwidth;
	//private int weight;
	private int[] availableBandwidth;
	private int[] wavelengthOwn;

	public WDMLink() {
		
	}
	public WDMLink(int id, int src, int dst, double delay, int wavelengths, int bw, int weight) {
		if (id < 0 || src < 0 || dst < 0 || wavelengths < 1 || bw < 1) {
            throw (new IllegalArgumentException());
        } else {
            this.id = id;
            this.source = src;
            this.destination = dst;
            this.delay = delay;
            this.wavelengths = wavelengths;
            this.maxBandwidth = bw;
            this.weight = bw;
            this.availableBandwidth = new int[wavelengths];
            this.wavelengthOwn = new int[wavelengths];
            for (int i = 0; i < wavelengths; i++) {
                this.availableBandwidth[i] = bw;
                this.wavelengthOwn[i] = -1;
            }
            
        }
	}

	public int getOwn(int wavelength) {
		return wavelengthOwn[wavelength];
	}
	
	public int getMaxBandwidth() {
		return maxBandwidth;
	}

	public void setMaxBandwidth(int maximumBandwidth) {
		this.maxBandwidth = maximumBandwidth;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getSrcId() {
		return source;
	}

	public void setSource(int source) {
		this.source = source;
	}

	public int getDstId() {
		return destination;
	}

	public void setDestination(int destination) {
		this.destination = destination;
	}
	
	public int getWeight() {
		return weight;
	}

	public void setWeight(int weight) {
		this.weight = weight;
	}

	// retorna se ha comprimento de onda com recursos maior que resources OU '0' caso não haja
	public boolean hasAvailableWavelength(int resources) {
		boolean r = false;
		for(int i=0; i<wavelengths; i++) {
			if(availableBandwidth[i]>=resources) {
				r = true;
				break;
			}
		}
		return r;
	}
	
	// retorna o total de comprimento de onda do enlace, somando o que está disponível em cada wavelenght
	public int getTotalAvailableBandwidth() {
		int availableBw = 0;
		for(int i=0; i<wavelengths; i++) {
			availableBw += availableBandwidth[i]; 
		}
		return availableBw;
	}

	public void setAvailableBandwidth(int[] availableBandwidth) {
		this.availableBandwidth = availableBandwidth;
	}
	
	
	public int getWavelengthAvailableBandwidth(int wavelength) {
		return availableBandwidth[wavelength];
	}

	// coloca o fluxo no primeiro wavelength com recursos suficientes
	public boolean insertFlow(int bw) {
		int resto = bw, i = 0;
		if(hasAvailableWavelength(bw)) {
			while(resto>0 && i<wavelengths) {
				if(availableBandwidth[i]>=bw) {
					resto = setFlowInWavelength(i, bw);
				}
				i++;
			}
			if(resto==0) return true;
			else return false;
		}
		else {
			return false;
		}
	}
	

	public boolean insertFlow(int wv, int bw) {
		int resto = bw;
		
		resto = setFlowInWavelength(wv, bw);
				
		if(resto==0) return true;
		else return false;
		
	}
	
	// divide o fluxo por varios comprimentos de onda do link
	public boolean insertFlowMultiwavelength(int bw) {
		if(this.hasAvailableWavelength(bw)) {
			int aux=0, resto=bw;
			while(resto>0 && aux<wavelengths) {
				resto = setFlowInWavelength(aux,bw);
				aux++;
			}
			if(resto==0) {
				return true;
			}
			else {
				return false;
			}
		}
		else {
			return false;
		}
	}

	/**
	 * reserva bandwidth em wavelength e retorna a quantidade de bandwidth que restou
	 * 
	 * @param wavelength
	 * @param bw
	 * @return
	 */
	private int setFlowInWavelength(int wavelength, int bw) {
		if(availableBandwidth[wavelength]>bw) {
			availableBandwidth[wavelength] -= bw;
			return 0;
		} else {
			int r = bw - availableBandwidth[wavelength];
			availableBandwidth[wavelength] = 0;
			return r;
		}
		
	}
	
	// reserva bandwidth em wavelength caso haja recusos suficientes, caso contrario apenas retorna false
	public boolean reserveResource(int wavelength, int bw, int src) {
		if(availableBandwidth[wavelength]>=bw && this.getOwn(wavelength)==-1) {
			availableBandwidth[wavelength] -= bw;
			wavelengthOwn[wavelength] = src;
			return true;
			
		} else {			
			return false;
		}
	}
	
	// reserva bandwidth em um wavelength e retorna o wavelength usado,
	// se nao houver wavelength disponivel retorna -1
	public int reserveResource(int bandwidth) {
		int wv = 0;
		boolean reserved = false;
		do {
			if(getWavelengthAvailableBandwidth(wv)>=bandwidth) {
				reserved = true;
				availableBandwidth[wv] -= bandwidth;
				return wv;
				
			} else {
				wv++;
			}
		} while (reserved==false && wv<=wavelengths);
		
		return -1;
	}

	public double getDelay() {
		return delay;
	}

	public void setDelay(double delay) {
		this.delay = delay;
	}

	public int getWavelengths() {
		return wavelengths;
	}

	public void setWavelengths(int wavelengths) {
		this.wavelengths = wavelengths;
	}

	public int getSource() {
		return source;
	}

	public int getDestination() {
		return destination;
	}

	public boolean release(int wavelength, Flow flow) {
		if(maxBandwidth-availableBandwidth[wavelength]>=flow.getBw()) {
			availableBandwidth[wavelength] += flow.getBw();
			if(availableBandwidth[wavelength]==maxBandwidth) {
				wavelengthOwn[wavelength] = -1;
			}
			return true;
		}
		else {
			return false;
		}
	}

	public boolean release(int wavelength, Flow flow, PCE pce) throws Exception {
		PhysicalTopology pt = PhysicalTopology.getInstance();
		if(pt.getDomainFromNode(source).getID() == pce.getId()) {
			if(maxBandwidth-availableBandwidth[wavelength]>=flow.getBw()) {
				availableBandwidth[wavelength] += flow.getBw();
				if(availableBandwidth[wavelength]==maxBandwidth) {
					wavelengthOwn[wavelength] = -1;
				}
				return true;
			}
			else {
				return false;
			}
		}
		
		return false;
	}

	/* (non-Javadoc)
	 * @see wdmsim.Link#getNumFreeWavelengths()
	 */
	@Override
	public int getNumFreeWavelengths() {
		int cont = 0;
		for(int i=0; i<wavelengths; i++) {
			if(availableBandwidth[i]==maxBandwidth) {
				cont++;
			}
		}
		return cont;
	}
	/**
	 * returns a vector contain the wavelengths not alocated
	 * @return
	 */
	public int[] getFreeWavelengths() {
		Vector<Integer> v = new Vector<Integer>();
		for(int i=0; i<wavelengths; i++) {
			if(availableBandwidth[i] == maxBandwidth) {
				v.add(i);
			}
		}
		int[] r = new int[v.size()];
		for(int i=0; i<v.size(); i++) {
			r[i] = v.get(i);
		}
		return r;
	}
	
	/**
	 * Pega os comprimentos de onda com o maximo largura de banda disponivel.
	 * Semelhante ao método anterior porem con o maximo disponivel de largura 
	 * de banda, e não o maximo total.
	 * @return Arranjo com os IDs dos comprimetos de onda.
	 */
	public int[] getWavelenghtsWithMaxAvailableBandWidth() {
		Vector<Integer> v = new Vector<Integer>();
		int max = -1;
		for(int i=0; i<wavelengths; i++) {
			if(availableBandwidth[i] > max ) {
				max = availableBandwidth[i];
			}
		}
		//Tratamento para wavelength com bandwidth = 0
		if (max == 0) {
			return null;
		}
		for(int i=0; i<wavelengths; i++) {
			if(availableBandwidth[i] == max ) {
				v.add(i);
			}
		}
		int[] r = new int[v.size()];
		for(int i=0; i<v.size(); i++) {
			r[i] = v.get(i);
		}
		return r;
	}

	/**
	 * @param wavelength
	 * @param bw
	 * @param src
	 * @return
	 */
	public boolean addFlow(int wavelength, int bw, int src) {
		if(availableBandwidth[wavelength]>=bw && this.getOwn(wavelength)==src) {
			availableBandwidth[wavelength] -= bw;
			return true;
			
		} else {			
			return false;
		}
	}
	
}
